/**
 * BulkSMSHTTPServletSbb
 * 
 * The main (root) SBB for the bulk sms service.
 * The SBB receives HTTP requests from the wild and sets parameters
 * Those parameters will be extracted from the activity context by Sbbs lower in the chain
 */
package org.convergence.services.slee.bulksms.http;
/**
 * @author mtsietsi
 *
 */
import java.io.IOException;
import java.io.PrintWriter;
import java.util.StringTokenizer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import javax.slee.ActivityContextInterface;
import javax.slee.ChildRelation;
import javax.slee.RolledBackContext;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import javax.slee.SbbID;
import javax.slee.SbbLocalObject;
import javax.slee.facilities.Tracer;

import org.mobicents.slee.SbbContextExt;

import net.java.slee.resource.http.events.HttpServletRequestEvent;

public abstract class BulkSMSHTTPServletSbb implements Sbb {
	
	private Tracer tracer;
	private String localQueryString;
	
	/* the following are set from the queryString in the GET operation */
	/* mandatory (always appearing) fields */
	private String localUsername;
	private String localPassword;
	private String localType;
	private String localOp;
	
	/* optional (not always appearing) fields */
	private String localName;
	private String localNewName;
	private String localNumber;
	private String localNewNumber;
	private String localGroup;
	private String localNewGroup;
	private String localTo;
	private String localText;
	private boolean localReport;

    // **** Activity Context Interface
    public abstract  HttpSbbActivityContextInterface asSbbActivityContextInterface(ActivityContextInterface aci);

	/*
	 * onServiceStartedEvent: invoked when the the SBB is loaded
	 * in: 
	 * 		ServicesStartedEvent: the type of event triggered by the SLEE when the SBB is loaded
	 * 		ActivityContextInterface: the activity context interface associated with the event 
	 */
	public void onServiceStartedEvent(javax.slee.serviceactivity.ServiceStartedEvent event, HttpSbbActivityContextInterface aci) {
		tracer.info("Bulk SMS HTTP Service Started");
	}
	
	/*
	 * OnGet: process the GET operation from an external system
	 * 		sets the parameter values in the HTTP request and sets activity attribute to true if successful or false if not
	 * in:
	 * 		HttpServletRequestEvent: the http type event from the RA
	 * 		ActivityContextInterface: the activity context associated with the event
	 */
	public void onGet(HttpServletRequestEvent event, HttpSbbActivityContextInterface aci) {
		tracer.info("Received GET operation");
		
        HttpServletRequest request = event.getRequest();
        localQueryString = request.getQueryString();
        
        try{
	        assignQueryStringParams(aci); /* assign aci attributes to local variables and do basic validation */
	        validateRequest(aci); /* use application specific rules to validate the request */
	        
	        aci.setProcessedByMe(true);
        }
        catch(TooFewParametersException tf) {
			tf.printStackTrace();
			aci.setProcessedByMe(false);
			processHttpRequest(event, "HTTP Error 432: Missing Mandatory Parameter in the request - Check API manual regarding: "+tf.getMessage());
		} 
        catch (ParameterValueMissingException ai) {
			ai.printStackTrace();
			aci.setProcessedByMe(false);
			processHttpRequest(event, "HTTP Error 433: Malformed URL - Missing Parameter Value - Check API manual regarding: "+ai.getMessage());
		} 
        catch(UnknownParameterException up) {
			up.printStackTrace();
			aci.setProcessedByMe(false);
			processHttpRequest(event, "HTTP Error 434: Unknown Parameter in the request - Check API manual regarding: "+ up.getMessage());
		}
        catch(InvalidRequestException ir) {
        	ir.printStackTrace();
			aci.setProcessedByMe(false);
			processHttpRequest(event, "HTTP Error 435: Illegal operation - Check API manual regarding: "+ir.getMessage());
        }
        catch(Exception ex) {
        	ex.printStackTrace();
			aci.setProcessedByMe(false);
			processHttpRequest(event, "Some general exception: "+ex.getMessage());
        }
        finally {
        	// detach from the activity context
        	SbbLocalObject sbbLocalObject = sbbContext.getSbbLocalObject();
            aci.detach(sbbLocalObject);
        }
	}

	/*
	 * (non-Javadoc)
	 * @see javax.slee.Sbb#setSbbContext(javax.slee.SbbContext)
	 * setSbbContext:	beginning of the life cycle of the SBB
	 * 				: 	here we set the tracer and null set variables to be used to store activity attributes
	 * 		in:
	 * 			SbbContext - an object passed to the SBB to invoke functions provided by the SLEE
	 */
	public void setSbbContext(SbbContext context) { 
		this.sbbContext = (SbbContextExt) context;
		tracer = sbbContext.getTracer(getClass().getSimpleName());
		localQueryString = localUsername = localPassword = localType = localOp = localName = localNewName = localNumber = localNewNumber = localGroup = localNewGroup = null;
	}
	
	/*
	 * (non-Javadoc)
	 * @see javax.slee.Sbb#unsetSbbContext()
	 * unssetSbbCOntext:	end of the life cycle of the SBB - SLEE releases references to the SBB here
	 * 		in:
	 * 			null
	 */ 
	public void unsetSbbContext() { this.sbbContext = null; }

	// TODO: Implement the lifecycle methods if required
	public void sbbCreate() throws javax.slee.CreateException {}
	public void sbbPostCreate() throws javax.slee.CreateException {}
	public void sbbActivate() {}
	public void sbbPassivate() {}
	public void sbbRemove() {}
	public void sbbLoad() {}
	public void sbbStore() {}
	public void sbbExceptionThrown(Exception exception, Object event, ActivityContextInterface activity) {}
	public void sbbRolledBack(RolledBackContext context) {}
	
	/**
	 * Convenience method to retrieve the SbbContext object stored in setSbbContext.
	 * 
	 * TODO: If your SBB doesn't require the SbbContext object you may remove this 
	 * method, the sbbContext variable and the variable assignment in setSbbContext().
	 *
	 * @return this SBB's SbbContext object
	 */
	
	/*
	 * processHTTTPRequest: creates an HTTP response event and responds to the request
	 * in: 
	 * 		ServicesStartedEvent: the event triggered by the SLEE when the SBB is loaded
	 * 		ActivityContextInterface: the activity context interface associated with the event 
	 */
	public void processHttpRequest(HttpServletRequestEvent event, String message) {
		try{
            HttpServletResponse response = event.getResponse();
            PrintWriter w = response.getWriter();
            w.print(message);
            w.flush();
            response.flushBuffer();
            tracer.info("HTTP Response sent");
		} 
		catch (IOException io) {
            io.printStackTrace();
		} 
	}
	
	/*
	 * assignQueryStringParams: sets the SBBs variables from the queryString
	 * 				mandatory for the URL to contain at least username, password, type and op parameters
	 * in:
	 * 		null
	 */
	public void assignQueryStringParams(HttpSbbActivityContextInterface aci) throws ParameterValueMissingException, UnknownParameterException {
		String token = new String();
		String [] pair;
		StringTokenizer strTok = new StringTokenizer(localQueryString, "&");
		
		while(strTok.hasMoreTokens()){
			token = strTok.nextToken();
			tracer.info(token);
			pair = token.split("=");
			
			//check to make sure we don't have something like param=&param=value
			if (pair.length != 2)
				throw new ParameterValueMissingException("one parameter in the url is null");
			
			// Waiting for JDK 7 that can do switch statements on Java Strings!!! in the meantime ...
			if(pair[0].compareTo("username") == 0) {
				localUsername = (String) pair[1]; 
				aci.setUsernameInHttp(localUsername);
			} 
			else if(pair[0].compareTo("password") == 0) {
				localPassword = (String) pair[1];
				aci.setPasswordInHttp(localPassword);
			}
			else if(pair[0].compareTo("type") == 0) {
				localType = (String) pair[1];
				aci.setTypeInHttp(localType);
			}
			else if(pair[0].compareTo("op") == 0) {
				localOp = (String) pair[1];
				aci.setOpInHttp(localOp);
			}
			else if(pair[0].compareTo("name") == 0) {
				localName = (String) pair[1];
				aci.setNameInHttp(localName);
			}
			else if(pair[0].compareTo("newname") == 0) {
				localNewName = (String) pair[1];
				aci.setNewNameInHttp(localNewName);
			}
			else if(pair[0].compareTo("number") == 0) {
				localNumber = (String) pair[1];
				aci.setNumberInHttp(localNumber);
			}
			else if(pair[0].compareTo("newnumber") == 0) {
				localNewNumber = (String) pair[1];
				aci.setNewNumberInHttp(localNewNumber);
			}
			else if(pair[0].compareTo("group") == 0) {
				localGroup = (String) pair[1];
				aci.setGroupInHttp(localGroup);
			}
			else if(pair[0].compareTo("newgroup") == 0) {
				localNewGroup = (String) pair[1];
				aci.setNewGroupInHttp(localNewGroup);
			}
			else if(pair[0].compareTo("to") == 0) {
				localTo = (String) pair[1];
				aci.setToInHttp(localTo);
			}
			else if(pair[0].compareTo("text") == 0) {
				localText = (String) pair[1];
				aci.setTextInHttp(localText);
			}
			else if(pair[0].compareTo("report") == 0) {
				localReport = Boolean.parseBoolean((String) pair[1]);
				aci.setReportInHttp(localReport);
			}
			else 
				throw new UnknownParameterException("weird unknown parameter: "+pair[0]);
		}
	}
	
	private void validateRequest(HttpSbbActivityContextInterface aci) throws TooFewParametersException, InvalidRequestException {
		/* the mandatory parameters in all requests for the service are username, password, type (contact, document, group, sms) and operation (add, delete, edit) */
		if(localUsername == null || localPassword == null || localType == null || localOp == null) {
			throw new TooFewParametersException("missing mandatory parameter in request");
		}
		
		//TODO No invalid checks on document requests
		if(localType.compareTo("document") == 0) {
			/* only add edit and delete operations permitted */
			if(localOp.compareTo("add") != 0 && localOp.compareTo("edit") != 0 && localOp.compareTo("del") != 0 && localOp.compareTo("get") != 0)
				throw new InvalidRequestException("only add edit and delete operations are permitted");
		}
		
		/* checks on group operations */
		else if (localType.compareTo("group") == 0) {
			
			/* if we are adding ....*/
			if(localOp.compareTo("add") == 0) {
				/* group must be specified */
				if(localGroup == null) throw new InvalidRequestException("when adding or deleting a group, the group parameter must be set");
			}
			/* if we are editing .... */
			else if(localOp.compareTo("edit") == 0) {
				/* group and new group MUST be specified */
				if(localGroup == null || localNewGroup == null) 
					throw new InvalidRequestException("when editing a group, the newgroup parameter must be set");
			}
			/* if we are deleting ...*/
			else if(localOp.compareTo("del") == 0) {
				/* group must be specified */
				if(localGroup == null) throw new InvalidRequestException("when adding or deleting a group, the group parameter must be set");
			}
			else{
				/* only add edit and delete permitted */
				throw new InvalidRequestException("only add edit and delete operations are permitted");
			}
		}
		
		else if(localType.compareTo("contact") == 0) {
			/* if we are adding .... */
			if(localOp.compareTo("add") == 0) {
				/* we must have a name and a contact number */
				if(localName == null || localNumber == null) throw new InvalidRequestException("when adding or deleting, name and contact number must be set");
				/* we add a group name when one has not been specified */
				if(localGroup == null) aci.setGroupInHttp("Ungrouped");
			}
			/* we are editing ... */
			else if(localOp.compareTo("edit") == 0) {
				/* if we are editing a name */
				if(localNewName != null && localName == null) throw new InvalidRequestException("when editing a name, the existing and new name must be set");
				/* if we are editing a number */
				if(localNewNumber != null && localNumber == null) throw new InvalidRequestException("when editing a number, a new number must be set");
				/* either name or contact must be changed */
				if(localNewName == null && localNewNumber == null) throw new InvalidRequestException("when editing a contact, either a new name or new number must be specified");
				/* we add a group name when one has not been specified */
				if(localGroup == null) aci.setGroupInHttp("Ungrouped");
			}
			/* we are deleting ... */
			else if(localOp.compareTo("del") == 0) {
				/* we must have a name and a contact number */
				if(localName == null || localNumber == null) throw new InvalidRequestException("when adding or deleting, name and a contact number must be set");
				/* we add a group name when one has not been specified */
				if(localGroup == null) aci.setGroupInHttp("Ungrouped");
			}
			else{
				/* only add edit and delete permitted */
				throw new InvalidRequestException("only add edit and delete operations are permitted");
			}
		}
		else if(localType.compareTo("sms") == 0) {
			/* only one mode of operation is valid which is send */
			if(localOp.compareTo("send") != 0) throw new InvalidRequestException("when sending an sms, only the send operation is permitted");
			/* either to or group must be set */
			if(localTo == null && localGroup == null) throw new TooFewParametersException("when sending an sms, to or group must be set");
			/* text must be set */
			if(localText == null) throw new TooFewParametersException("when sending an sms, text must be set");
		}
	}
	
	protected SbbContextExt getSbbContext() {
		return sbbContext;
	}

	private SbbContextExt sbbContext; // This SBB's SbbContext
	
	/*
	 * getSbbId: returns the SBB identifier
	 * out:
	 * 		sbbId - the identifier of the SBB in String format
	 */
	private final String getSbbId() {
        SbbID sbbId = this.sbbContext.getSbb();
        return sbbId.toString();
	}
}
